Skip to content

release: Bazarr+ v2.0.0 (Eclipse)#49

Merged
LavX merged 243 commits intomasterfrom
development
Mar 30, 2026
Merged

release: Bazarr+ v2.0.0 (Eclipse)#49
LavX merged 243 commits intomasterfrom
development

Conversation

@LavX
Copy link
Copy Markdown
Owner

@LavX LavX commented Mar 30, 2026

Bazarr+ v2.0.0 (Eclipse)

First stable release as an independent hard fork. 197 commits since v1.5.6-lavx "Neon Pulse".


Fork-Exclusive Features

Provider Priority Search
Dual mode: sequential priority order with early stop when minimum score is met, or classic simultaneous search. Requested upstream for 6 years (62 votes), rejected as "won't happen".

OpenSubtitles.org Web Scraper
Self-hosted FastAPI microservice via CloudScraper with FlareSolverr fallback. Full v1 REST API with metadata-aware search, intelligent result ranking, 30-language mapping. No API key or VIP needed.

AI Subtitle Translation (OpenRouter)
Access to 300+ LLMs (Claude, Gemini, GPT, LLaMA, Grok) plus custom model IDs. Dedicated 4-zone settings page with pricing, cost estimates, token usage, speed metrics. Batch translation for entire series/libraries. Translate-from-missing menu with source language picker.

11 Batch Operations
Sync, translate, OCR fixes, common fixes, remove HI, remove tags, fix uppercase, reverse RTL, emoji removal, scan disk, search missing, upgrade. Supports up to 10,000 items per batch with real-time progress.

Subtitle Viewer
Read-only preview with SRT/VTT/ASS parsing, cue table, timestamps, file size, format detection.

Per-Provider Progress Tracking
Subtitle searches show which provider is being queried in real-time (e.g. "Searching opensubtitles (2/9)").

Mass Subtitle Sync
Sync entire libraries from System Tasks or Mass Edit. Requested upstream (249 votes), rejected.

Advanced Table Filters
Include/exclude audio language (multi-select), missing subtitle filter, title search with active filter chips.

AES-256-GCM API Key Encryption
Encryption for translator API keys in transit with HMAC-SHA256 authentication.


Security Hardening (10 areas)

  • PBKDF2-SHA256 password hashing (600k iterations, 16-byte salt) replacing MD5
  • CSRF protection with cryptographic state tokens in Plex OAuth
  • SSRF blocking with DNS pinning and IP validation on /test proxy
  • Brute-force protection (5 failed attempts = 5-minute lockout per IP)
  • Shell injection prevention with shlex.quote()
  • Filesystem sandboxing (blocks /proc, /sys, /dev, /etc, /root, /tmp)
  • eval() removal in throttled provider cache (replaced with JSON)
  • Constant-time API key comparison with hmac.compare_digest()
  • Per-service TLS verification toggle
  • All Google Analytics tracking removed (GA4 + legacy UA)

Atmospheric Dark Redesign

  • Navy (#121125) to cream (#fff8e1) palette with amber accents
  • Geist Sans variable font replacing Roboto
  • Grain texture overlay and ambient atmospheric glow
  • Redesigned sidebar with grouped sections and active indicators
  • Ghost-style header buttons with refined borders
  • Fanart bleed effect with gradient fade
  • Comprehensive design token system (text hierarchy, radius scale, spacing)
  • Full Mantine color palette override
  • Light mode support with proper contrast
  • Reduced motion support

Upstream Sync (this release)

Cherry-picked 24 commits from morpheus65535/bazarr development:

  • Security: RCE fix (pickle deserialization replaced with server-side cache), serialize-javascript override
  • Features: Two point alignment mods, wait_for_completion across jobs, Gemini multi-key rotation + batch size, scoring system refactor with score_out_of column, Sonarr sync optimization with GC
  • Bug fixes: OpenSubtitles conditional logic, SubX NoneType, SubsSabBz 403 retry, child termination, legendasnet parsing, Sonarr/Radarr v3 fallback, file permissions, throttling, notifier duplicates
  • Dependencies: All frontend deps bumped (React 19.2, Vite 7.3, ESLint 10, Mantine 8.3.16, TypeScript 5.9), vendored Python libs bulk update (3229 files)

Bug Fixes (this release)

  • Batch endpoint crash from jsonschema/flask_restx incompatibility
  • React-router 7.13 blocker state transition error ("unblocked -> proceeding")
  • Provider throttling crash (time_until_midnight callers passing datetime instead of timezone)
  • Upgrade subtitle path fallback: finds current file when history path is stale (renamed/re-downloaded subs)
  • Null score guard for manual download history entries
  • Announcements fetch writing on both CDN and GitHub fallback
  • Default minimum score lowered from 90% to 80% (scoring refactor shifted weight distribution)
  • OpenSubtitles scraper health check on provider init (shows "Unavailable" instead of silent failures)
  • bazarr.py restart handler: terminate_child moved out of finally block

Infrastructure

  • Python 3.14 runtime (minimum 3.12)
  • ESLint 8 to 10 flat config migration
  • bazarr-binaries submodule for announcements
  • Self-built multi-arch Docker images on GHCR
  • Independent versioning (v2.0.0+)

Verification

  • 96/96 frontend tests pass
  • TypeScript: 0 errors
  • ESLint: 0 errors
  • Vite build: passes
  • Deployed and tested on test server
  • Provider search with per-provider progress confirmed
  • Upgrade subtitle path fallback confirmed
  • Announcements displaying correctly
  • Scraper health check confirmed
  • Beta image built and tested from GHCR

LavX and others added 30 commits March 20, 2026 00:59
…king

- Replace upstream username with neutral terms ("upstream") in display text
- Update binaries.json and announcements.py to use forked LavX/bazarr-binaries
- Remove PayPal donate button from UI navbar and README
- Remove Feature Upvote and Discord links from issue template
- Update frontend/package.json author and URLs to this fork
- Rewrite "Why This Fork Exists" to focus on technical gap, not PR history
- Collapse original README section to just the provider list
- Update copyright years to 2025-2026
chore: minimize upstream maintainer mentions
When batch translation cannot find an external subtitle file for the
source language, it now checks for embedded subtitle tracks in the video
container. If found, it extracts the track to an SRT file using ffmpeg
and uses it as the translation source.

This enables the Mass Translate feature to work with media that only has
embedded subtitles (stored as [lang, None, None] in the DB).

Changes:
- Add extract_embedded_subtitle() that uses ffprobe metadata to find the
  correct subtitle stream and ffmpeg to extract it to SRT
- Add a third pass in find_subtitle_by_language() for embedded subs
- Extract to /config/extracted_subs/ to avoid Jellyfin picking them up
- Strip Windows carriage returns from extracted subtitles
- Resolve alpha2 language codes to full names before sending to the AI
  translator (e.g. "pb" -> "Portuguese (Brazil)")

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- New logo: play button with smiling subtitle lines, golden gradient on dark
- Generated all icon sizes: favicon, PWA, apple-touch, mstile, android-chrome
- Updated brand color palette from purple to golden/amber (#ff9900) to match logo
- Added source SVG for future edits
The custom _escape() function did not escape shell metacharacters like
backticks and dollar-sign substitution inside double-quoted strings,
allowing potential command injection via externally-sourced subtitle
provider data when post-processing is enabled. Replace with shlex.quote()
which properly handles all shell special characters.
The throttled_providers.dat file was deserialized using eval(), which
could execute arbitrary code if the file contents were tampered with.
Replace with JSON serialization using json.loads()/json.dumps() and
store datetime objects as ISO 8601 strings.
The CSRF state validation for Plex OAuth PIN checks only logged a
warning but did not reject the request. Add abort(403) to actually
block requests that fail state validation.
Replace unsalted MD5 password hashing with PBKDF2-SHA256 (150k
iterations, random 16-byte salt, timing-safe comparison).

- Legacy MD5 hashes (upstream format) are always accepted for login
- On login with MD5 hash, UI prompts user to upgrade (modal dialog)
- User can decline; prompt reappears on next login
- Upgrade action re-hashes password and persists to config
- New passwords set via settings always use PBKDF2
- Upstream bazarr users can migrate seamlessly (MD5 accepted on first login)
Add in-memory per-IP failed login attempt tracking. After 5 failed
attempts within 5 minutes, the IP is locked out with HTTP 429. Counter
resets on successful login or after lockout period expires.
No new dependencies required (uses stdlib threading and time).
Add URL validation to the /test proxy endpoint to block requests
to link-local addresses (169.254.x.x) which includes cloud metadata
endpoints. Private LAN IPs are still allowed since the endpoint is
used to test connectivity to Sonarr/Radarr/Plex instances.
…wser

Filter out /proc, /sys, /dev, /run, /snap, /boot, and /lost+found from
the filesystem browser API. These directories expose system internals
and are never valid media paths. Users can still browse /home, /mnt,
/media, /data, and other typical media storage locations.
Add verify_ssl config option for sonarr, radarr, and plex services
(default: false for backward compatibility). Replace all hardcoded
verify=False with get_ssl_verify() calls that read from config.
Users with proper TLS certificates can now enable verification.
Prefer X-API-KEY header for authentication. Query string and form data
API keys still work for backward compatibility but now log a deprecation
warning with the endpoint path. Header auth is checked first to avoid
unnecessary logging for well-behaved clients.
shlex.quote() is POSIX-only and produces single quotes that cmd.exe
does not understand. Use subprocess.list2cmdline() on Windows for
proper double-quote escaping. Also handle None inputs explicitly
rather than converting to the string 'None'.
- Remove string fallback in set_throttled_providers (require dict)
- Add None guard on throttle_until.isoformat()
- Use atomic write (tmp file + os.replace)
- Better migration log message for legacy format files
- Remove finally:return antipattern
- Extract path to _throttled_providers_path() helper
- Catch specific exceptions (JSONDecodeError, KeyError, ValueError)
  separately from unexpected errors
Flip the guard logic: check if the server issued a state_token for
this PIN (stored_state exists), and if so, require the client to
provide a matching state param. Previously an attacker could bypass
by simply omitting the state parameter.

Also: remove redundant 'return' before abort(), use generic error
message to avoid leaking implementation details.
Address review findings:
- Fix AxiosResponse unwrap (return response.data, not response)
- Replace plaintext password in sessionStorage with opaque token
- Server stores password in Flask session (server-side only)
- Upgrade action uses token to retrieve password from session
- No credentials ever stored client-side or re-transmitted
- Increase PBKDF2-SHA256 iterations from 150k to 600k per OWASP 2023
- Extract iteration count to PBKDF2_ITERATIONS constant (no duplication)
- Rollback in-memory password on write_config() failure to prevent
  split-brain state between running process and config file
The frontend received the state token from createPin() but never
sent it back in checkPin() requests. Now the state is threaded
through: Pin type -> usePlexPinCheckQuery -> checkPin API call.
Combined with the server-side guard that requires state when issued,
this closes the CSRF bypass.
- Use request.remote_addr only (not X-Forwarded-For/X-Real-IP) to
  prevent trivial rate limit bypass via header spoofing
- Cap _login_attempts dict at 10k entries using OrderedDict with
  LRU eviction to prevent memory exhaustion from distributed attacks
Address review findings:
- Resolve DNS once and pin request to resolved IP to prevent TOCTOU
  DNS rebinding attacks (attacker returns safe IP on first query,
  metadata IP on second)
- Block both link-local AND loopback addresses (not just link-local)
- Fail-closed: block request if DNS resolution fails (was fail-open)
- Use Host header to maintain proper routing with pinned IP
Use settings.get('service.verify_ssl') instead of chained .get()
which could silently return False on typos. Add service name
validation with frozenset allowlist.
- Use hmac.compare_digest() for API key comparison to prevent
  timing side-channel attacks
- Use %-formatting in logging.warning() instead of f-string to
  prevent potential log injection via request.path
- Use request.headers.get() instead of manual 'in' check
/etc contains sensitive configs (shadow, passwd, keys), /root is the
root user's home directory, /tmp may contain session data. None are
valid media storage paths.
If the stored hash has 'pbkdf2:' prefix but malformed data (bad hex,
missing colon), catch ValueError/TypeError and return False with a
log message instead of crashing on every login attempt.
Use lambda in re.sub to avoid backslash escape interpretation in
replacement strings. Windows paths like C:\Program Files would have
\P treated as a bad regex escape. The lambda returns the escaped
value literally without re.sub processing backslashes.
On dual-stack hosts, a .local hostname may resolve to both a private
LAN address and a link-local IPv6 address. Instead of rejecting if
ANY address is link-local, find a safe (non-link-local, non-loopback)
address to pin to. Only reject if ALL addresses are blocked.
Linux systems using GNOME/KDE automount removable and network media
under /run/media/. Blocking /run entirely prevents the file picker
from reaching those valid storage locations.
Bazarr's own Plex webhook URLs use ?apikey= in their callback URLs.
Skip the deprecation warning for /webhooks/ paths to avoid log spam
from every Plex event.
fix(security): shell injection in post-processing
anderson-oki and others added 28 commits March 30, 2026 00:34
…er tasks to improve feedback in System-->Tasks and prevent users from starting the same task multiple times.
…` parameter to streamline update checks during application initialization.
… operations. Added garbage collection after sync tasks to improve memory management. morpheus65535#3241
…e impact of movie edition. Removed the custom scoring system and cleaned up the config file. Updated history tables schema to include `score_out_of` column for improved normalization of score percentage over time. morpheus65535#3232
… flat config

Batch update of all frontend dependencies to match upstream development:
- Mantine 8.3.9 -> 8.3.16, React 19.2.4, React Router 7.13.1
- ESLint 8 -> 10 with flat config (eslint.config.mjs replaces .eslintrc.json)
- TypeScript 5.9.3, Vite 7.3.1, Vitest 4.0.18, Recharts 3.8.0
- FontAwesome 7.2.0, TanStack Query 5.90.21, axios 1.13.6
- Remove deprecated ban-types disable comments, fix jest v30 compat
- Drop eslint checker from vite-plugin-checker (matches upstream)
Cherry-pick of upstream 27ea75e: bulk update of all vendored Python
libraries in libs/. Kept our fork's Python 3.12 minimum requirement
(stricter than upstream's 3.10). Updated CI matrices to match.
Removed ga4mp analytics dist-info that was re-added by upstream
(we intentionally removed this library from our fork).
- Remove flask_restx model validation decorator from batch POST
  to fix jsonschema/flask_restx incompatibility (registry vs resolver)
- Update hardcoded score thresholds to use dynamic score_out_of column
- Guard react-router blocker proceed/reset calls with state check
  to fix "Invalid blocker state transition" error in react-router 7.13
- Increase vitest timeout and use forks pool for test stability
chore: sync vendored Python libs from upstream
…uard

- When a subtitle file has been renamed or re-downloaded (e.g. .hu.hi.srt
  to .hu.srt), the upgrade system now finds the current file for the same
  language instead of silently dropping the candidate.
- Fix time_until_midnight callers passing datetime instead of timezone
  (regression from upstream cherry-pick of 263d01c).
- Guard against None score in forced_minimum_score to prevent TypeError
  on manual download history entries.
Show which provider is being searched in real-time in the jobs manager
(e.g. "Searching opensubtitles (2/5)"). Adds a provider_progress_callback
on the provider pool that gets called before each provider query.
Both individual and specific subtitle searches now report progress.
The terminate/restart logic should not be inside finally, which
runs even if os.remove fails, masking the exception.
Verify the scraper service is reachable during provider initialization
by hitting /health. If unreachable, raise ServiceUnavailable so the
provider is throttled for 20 minutes and shows the issue in
System > Providers, instead of silently failing on every search.
fix: upgrade path fallback, provider progress tracking
The upstream scoring refactor shifted weight from series name matching
to source/release_group matching, making 90% unreachable for subtitles
that match on series/year/season/episode but not release specifics.
When build_type is 'custom', the VERSION file was set to the custom
tag name (e.g. 'beta') instead of the actual version. Now uses
version_tag input when provided (e.g. 'v2.0.0-beta.1').
The else block only ran on jsdelivr success, so the fallback to raw
GitHub would fetch but never save the file. Also add raise_for_status
to catch HTTP errors.
Repository owner deleted a comment from chatgpt-codex-connector bot Mar 30, 2026
@LavX LavX merged commit 2a335eb into master Mar 30, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

10 participants